package org.wikipedia.miner.util;

import java.io.File;
import java.io.FileWriter;
import java.io.PrintStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;

/**
 * The main function of this class is to calculate the distance among a list of
 * categories. The result is a matrix where element[i][j] is the relationship
 * between category[i] and category[j].
 * 
 * #nick This class can be further optimized performance wise, e.g., when
 * calculate intersection of articles and categories. #nick Also currently, it
 * only calculates the descendent intersection, ideally, the ancestor
 * intersection can also be calculated
 * 
 * @author nick
 * 
 */
public class ResultData {

	/** choose the WikiDB implementation */
	public static WikiDB wikiDB = WikiDB.getDB(WikiDB.DBType.OLDWIKIMINERDB);
	public static int level = 3;

	public static String outputDir = "D:/wikidb/data1/wikidb09";

	public String[] categoryStrings;

	/** the articles that belong to category[i] */
	public ArrayList<Integer>[] categoryArticles;

	/** the linksOuts that belong to category[i] #ni */
	public ArrayList<Integer>[] categoryLinksOuts;

	/** the categoryTitles that belong to category[i] #ni */
	public ArrayList<String>[] categoryTitles;

	/* the reference count from category[i] to category[j] */
	public int[][] categoryReferenceCount;

	/** the intersection count of category[i] and category[j] */
	public int[][] categoryIntersectionsCount;

	/** the intersection count of article[i] and artcle[j] */
	public int[][] articleIntersectionsCount;

	/**
	 * The given array is the category list
	 * 
	 * @param categoryStrings
	 *            the category list that the distances will be calculated
	 */
	public ResultData(String[] categoryStrings) {
		this.categoryStrings = categoryStrings;
		int ml = categoryStrings.length;

		categoryReferenceCount = new int[ml][ml];

		categoryIntersectionsCount = new int[ml][ml];

		articleIntersectionsCount = new int[ml][ml];

		categoryArticles = new ArrayList[ml];

		categoryLinksOuts = new ArrayList[ml];

		categoryTitles = new ArrayList[ml];

		for (int i = 0; i < categoryStrings.length; i++) {
			String cat = categoryStrings[i];

			ArrayList<Integer> articleList = wikiDB
					.getDistinctDescendentArticles(cat, level);
			System.out.println("Category: " + cat + " has "
					+ articleList.size() + " descendent articles");
			categoryArticles[i] = articleList;

			ArrayList<Integer> linksOutList = wikiDB.getCategoryLinksOut(cat,
					level);
			System.out.println("Category: " + cat + " has "
					+ linksOutList.size() + " descendent linksOuts");
			categoryLinksOuts[i] = linksOutList;

			ArrayList<String> categoryTitleList = wikiDB
					.getDescendentCategoryTitles(cat, level);
			System.out.println("Category: " + cat + " has "
					+ categoryTitleList.size() + " descendent categoryTitles");
			categoryTitles[i] = categoryTitleList;
			// articleList = CategoryWrapper.getTotalDescendentArticles(cat,
			// level);
			// System.out.println("Category: "+cat.getTitle()+" articles (non-unique): "
			// + articleList.size());

		}
	}

	public static void main(String args[]) throws Exception {
		System.out.println("starting ResultData ...");

		DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
		System.out.println(dateFormat.format(new Date()));

		// Category[] arrayOfCategories = new
		// Category[ResultData.categoryStrings.length];
		// make an array of Categories that are interested
		String[] categoryStrings = { "Philosophy", "Logic", "Mathematics",
				"Physics", "Chemistry", "Biology", "Computer science",
				"Psychology", "Linguistics", "Sociology", "History",
				"Economics", "Political science", "Literature" };

		ResultData resultData = new ResultData(categoryStrings);

		// for a category A to category B, find: 1) inter-references, 2)
		// intersections both articles and categories
		for (int i = 0; i < resultData.categoryStrings.length; i++) {
			// Category catA = arrayOfCategories[i];
			String catA = resultData.categoryStrings[i];

			for (int j = i; j < resultData.categoryStrings.length; j++) {
				long ct = System.currentTimeMillis();
				if (i == j) { // self reference
					// CategoryReference t1 = new CategoryReference(catA, catA);
					/* #ni */
					CategoryReference t1 = new CategoryReference(
							resultData.categoryLinksOuts[i],
							resultData.categoryArticles[i]);
					t1.run();

					resultData.categoryReferenceCount[i][j] = t1.referenceCount;
					System.out.println("From Category " + catA
							+ "  to Category " + catA + "  "
							+ t1.referenceCount);

					t1.printResults(10);
					System.out.println("running time: "
							+ (System.currentTimeMillis() - ct));
					continue;
				}

				String catB = resultData.categoryStrings[j];

				// CategoryReference t1 = new CategoryReference(catA, catB);
				/* #ni */
				CategoryReference t1 = new CategoryReference(
						resultData.categoryLinksOuts[i],
						resultData.categoryArticles[j]);
				t1.start();
				// CategoryReference t2 = new CategoryReference(catB, catA);
				CategoryReference t2 = new CategoryReference(
						resultData.categoryLinksOuts[j],
						resultData.categoryArticles[i]);
				t2.start();

				// IntersectArticles ia = new IntersectArticles(catA, catB);
				IntersectArticles ia = new IntersectArticles(
						resultData.categoryArticles[i],
						resultData.categoryArticles[j]);
				ia.start();

				// IntersectCategories ic = new IntersectCategories(catA, catB,
				// level, (level + 1));
				IntersectCategories ic = new IntersectCategories(
						resultData.categoryTitles[i],
						resultData.categoryTitles[j], level, (level + 1));
				ic.start();

				try {
					t1.join();
					t2.join();

					ia.join();
					ic.join();
				} catch (InterruptedException ie) {
					System.out.println(ie);
				}

				resultData.categoryReferenceCount[i][j] = t1.referenceCount;
				resultData.categoryReferenceCount[j][i] = t2.referenceCount;

				resultData.articleIntersectionsCount[i][j] = ia
						.getIntersectionCount();
				resultData.categoryIntersectionsCount[i][j] = ic.downIntersect
						.size();

				System.out.println("From Category " + catA + "  to Category "
						+ catB + "  " + t1.referenceCount);
				t1.printResults(10);

				System.out.println("From Category " + catB + "  to Category "
						+ catA + "  " + t2.referenceCount);
				t2.printResults(10);

				System.out.println("Article Intersection of Category: " + catA
						+ " and Category: " + catB + " has " + ia.ret.size()
						+ " Articles.");
				ia.printResults(10);

				ic.printResults(10);
				System.out.println("running time: "
						+ (System.currentTimeMillis() - ct));
			}
		}

		printCSV(outputDir + File.separator + "CategoryReference.csv",
				resultData.categoryStrings, resultData.categoryReferenceCount);
		printCSV(outputDir + File.separator + "CategoryIntersection.csv",
				resultData.categoryStrings,
				resultData.categoryIntersectionsCount);
		printCSV(outputDir + File.separator + "ArticleIntersection.csv",
				resultData.categoryStrings,
				resultData.articleIntersectionsCount);

		System.out.println(dateFormat.format(new Date()));
		wikiDB.close();
	}

	static void init() throws Exception {

		File f = new File(outputDir);
		if (!f.exists())
			f.mkdir();
		String consoleFile = outputDir + File.separator + "consoleFile";
		PrintStream ps = new PrintStream(consoleFile);
		System.setOut(ps);
	}

	/** print the matrix in CSV format */
	static void printCSV(String fileName, String[] categoryStrings, int[][] tbp)
			throws IOException {

		File f = new File(fileName);

		// if file doesnt exists, then create it
		if (!f.exists()) {
			f.createNewFile();
		}

		FileWriter fw = new FileWriter(f);

		String headline = "Categories,";
		for (int i = 0; i < categoryStrings.length; i++) {
			headline += "\"" + categoryStrings[i] + "\"";
			if (i < (categoryStrings.length - 1))
				headline += ",";
		}
		headline += "\r\n";

		fw.write(headline);

		for (int i = 0; i < categoryStrings.length; i++) {
			String strline = "\"" + categoryStrings[i] + "\",";
			for (int j = 0; j < categoryStrings.length; j++) {
				strline += tbp[i][j];
				if (j < (categoryStrings.length - 1))
					strline += ",";
			}
			strline += "\r\n";
			fw.write(strline);
		}

		fw.close();
	}
}

/**
 * calculate the reference from one category to another
 * 
 * @author nick
 * 
 */
class CategoryReference extends Thread {
	String catA, catB;

	public int referenceCount;

	PageId[] referencingList;
	PageId[] referencedList;

	/* #ni */
	ArrayList<Integer> catAList;// the linksout articles belong to category[i]
	ArrayList<Integer> catBList;// the articles belong to category[i]

	public CategoryReference(String catA, String catB) { // ArrayList<Integer>
															// catAList,
															// ArrayList<Integer>
															// catBList) {
		this.catA = catA;
		this.catB = catB;

	}

	/* #ni */
	public CategoryReference(ArrayList<Integer> catAList,
			ArrayList<Integer> catBList) {
		this.catAList = catAList;
		this.catBList = catBList;
	}

	public void run() {
		/* #ni */
		if (null == catA || catA.trim().equals("")) {
			referenceCount = linkListFromCategoryToCategory(catAList, catBList);
		} else {
			referenceCount = linksFromCategoryToCategory(catA, catB);
		}
	}

	int linksFromCategoryToCategory(String catA, String catB) { // ArrayList<Integer>
																// catAList,
																// ArrayList<Integer>
																// catBList) {
		ArrayList<Integer> catALinksOut = ResultData.wikiDB
				.getCategoryLinksOut(catA, ResultData.level);
		ArrayList<Integer> catBList = ResultData.wikiDB
				.getDistinctDescendentArticles(catB, ResultData.level);

		referencingList = new PageId[catALinksOut.size()];
		referencedList = new PageId[catBList.size()];

		for (int i = 0; i < catALinksOut.size(); i++) {
			referencingList[i] = new PageId(catALinksOut.get(i), 0);
		}

		for (int i = 0; i < catBList.size(); i++) {
			referencedList[i] = new PageId(catBList.get(i), 0);
		}

		int ret = 0;

		if (catALinksOut == null)
			return ret;

		for (int j = 0; j < catALinksOut.size(); j++) {
			for (int k = 0; k < catBList.size(); k++) {
				int artB = catBList.get(k);
				if (artB == catALinksOut.get(j)) {
					referencingList[j].count++;
					referencedList[k].count++;
					ret++;
				}
			}
		}

		Arrays.sort(referencingList);
		Arrays.sort(referencedList);

		return ret;
	}

	/* #ni */
	int linkListFromCategoryToCategory(ArrayList<Integer> catALinksOut,
			ArrayList<Integer> catBList) { // ArrayList<Integer>
											// catAList,
											// ArrayList<Integer>
											// catBList) {
		// ArrayList<Integer> catALinksOut = ResultData.wikiDB
		// .getCategoryLinksOut(catA, ResultData.level);
		// ArrayList<Integer> catBList = ResultData.wikiDB
		// .getDistinctDescendentArticles(catB, ResultData.level);

		referencingList = new PageId[catALinksOut.size()];
		referencedList = new PageId[catBList.size()];

		for (int i = 0; i < catALinksOut.size(); i++) {
			referencingList[i] = new PageId(catALinksOut.get(i), 0);
		}

		for (int i = 0; i < catBList.size(); i++) {
			referencedList[i] = new PageId(catBList.get(i), 0);
		}

		int ret = 0;

		if (catALinksOut == null)
			return ret;

		for (int j = 0; j < catALinksOut.size(); j++) {
			for (int k = 0; k < catBList.size(); k++) {
				int artB = catBList.get(k);
				if (artB == catALinksOut.get(j)) {
					referencingList[j].count++;
					referencedList[k].count++;
					ret++;
				}
			}
		}

		Arrays.sort(referencingList);
		Arrays.sort(referencedList);

		return ret;
	}

	public void printResults(int topCount) {

		System.out.println("Top referencing articles:");
		printTop(referencingList, topCount);
		System.out.println("Top referenced articles:");
		printTop(referencedList, topCount);
	}

	void printTop(PageId[] pidList, int topCount) {
		for (int i = 0; i < topCount; i++) {
			if (i >= pidList.length)
				return;

			PageId pid = pidList[i];
			String p = ResultData.wikiDB.getArticleById(pid.pageId);
			System.out.println("\t" + p + " " + pid.count);
		}
	}

}

class PageId implements Comparable<PageId> {
	int pageId = 0;
	int count = 0;

	public PageId(int pageId, int count) {
		this.pageId = pageId;
		this.count = count;
	}

	public int compareTo(PageId b) {
		return (b.count - count);
	}
}

/**
 * calculate the intersection of all articles under one category to all articles
 * under another
 * 
 * @author nick
 */
class IntersectArticles extends Thread {
	ArrayList<Integer> catAList, catBList;
	public ArrayList<Integer> ret;

	public int getIntersectionCount() {
		return ret.size();
	}

	public IntersectArticles(String catA, String catB) { // ArrayList <Article>
															// catAList,
															// ArrayList
															// <Article>
															// catBList) {
		catAList = ResultData.wikiDB.getDistinctDescendentArticles(catA,
				ResultData.level);
		catBList = ResultData.wikiDB.getDistinctDescendentArticles(catB,
				ResultData.level);
	}

	/* #ni */
	public IntersectArticles(ArrayList<Integer> catAList,
			ArrayList<Integer> catBList) { // ArrayList <Article>
											// catAList,
											// ArrayList
											// <Article>
											// catBList)
		this.catAList = catAList;
		this.catBList = catBList;
	}

	/**
	 * calculating the intersection of the articles contained in the catA and
	 * catB
	 * 
	 * @param catA
	 * @param catB
	 * @param level
	 */
	public void run() {
		ret = intersect(catAList, catBList);
	}

	ArrayList<Integer> intersect(ArrayList<Integer> l1, ArrayList<Integer> l2) {
		ArrayList<Integer> ret = new ArrayList<Integer>();
		for (int i = 0; i < l1.size(); i++) {
			int c1 = l1.get(i);
			if (l2.contains(c1)) {
				ret.add(c1);
			}
		}
		return ret;
	}

	public void printResults(int topCount) {

		for (int i = 0; i < topCount; i++) {
			if (i >= ret.size())
				return;
			String p = ResultData.wikiDB.getArticleById(ret.get(i));
			System.out.println("\t" + p);
		}
	}
}

/**
 * calculate intersection of all sub categories under one category to all sub
 * categories under another category
 * 
 * @author nick
 * 
 */
class IntersectCategories extends Thread {
	public String catA, catB;
	public int downLevel, upLevel;

	public ArrayList<String> upIntersect, downIntersect;

	/* the categoryTitles belong to category[i] #ni */
	ArrayList<String> catAList, catBList;

	public IntersectCategories(String catA, String catB, int downLevel,
			int upLevel) {
		this.catA = catA;
		this.catB = catB;
		/* #ni */
		catAList = ResultData.wikiDB.getDescendentCategoryTitles(catA,
				downLevel);
		catBList = ResultData.wikiDB.getDescendentCategoryTitles(catB,
				downLevel);
		this.downLevel = downLevel;
		this.upLevel = upLevel;
	}

	/* #ni */
	public IntersectCategories(ArrayList<String> catAList,
			ArrayList<String> catBList, int downLevel, int upLevel) {
		this.catAList = catAList;
		this.catBList = catBList;
		this.downLevel = downLevel;
		this.upLevel = upLevel;
	}

	/**
	 * calculating the intersection of the articles contained in the catA and
	 * catB
	 * 
	 * @param catA
	 * @param catB
	 * @param level
	 */
	public void run() {

		downIntersect = intersect(catAList, catBList);
		/*
		 * catAList = CategoryWrapper.getAncestors(catA, upLevel); catBList =
		 * CategoryWrapper.getAncestors(catB, upLevel); upIntersect =
		 * CategoryWrapper.getIntersection(catAList, catBList);
		 */
	}

	ArrayList<String> intersect(ArrayList<String> l1, ArrayList<String> l2) {
		ArrayList<String> ret = new ArrayList<String>();
		for (int i = 0; i < l1.size(); i++) {
			String c1 = l1.get(i);
			if (l2.contains(c1)) {
				ret.add(c1);
			}
		}
		return ret;
	}

	public void printResults(int topCount) {
		System.out.println("Category Intersection of Category: " + catA
				+ " and Category: " + catB + " has " + downIntersect.size()
				+ " Categories.");
		for (int i = 0; i < topCount; i++) {
			if (i >= downIntersect.size())
				return;
			System.out.println("\t" + downIntersect.get(i));
		}

		if (upIntersect == null)
			return;
		System.out.println("Ancestor Category Intersection of Category: "
				+ catA + " and Category: " + catB + " has "
				+ upIntersect.size() + " Categories.");
		for (int i = 0; i < topCount; i++) {
			if (i >= upIntersect.size())
				return;
			System.out.println("\t" + upIntersect.get(i));
		}
	}
}
